﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SaunaClient.Model.Logger;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net.Sockets;

namespace SaunaClient.Model.Network
{
    /// <summary>
    /// TCP/IP DAO Communication Class
    /// </summary>
    /// <dates>
    /// 28/09/2011 - created by Chiaki Sato
    /// </dates>
    /// <remarks>
    /// Note:
    /// This class is thread safe. 
    /// </remarks>
    public class TcpDaoGatewaySsl : TcpBaseSetting, ITcpDaoGateway
    {
        // Constant
        private const string FILE_LOGGER = "FileLogger";
        private const string TCP_LOG_MESSAGE = "Command: {0}";
        private const int MAX_RECEIVE_DATA_SIZE = 4096;
        private const string PROTOCOL_PREFIX = "$SAU";
        private const string PROTOCOL_LOGIN_REQUEST = "LI";
        private const string PROTOCOL_LOGIN_RESPONSE = "LR";
        private const string PROTOCOL_LOGIN_RESULT_BAD = "BAD";
        private const string PROTOCOL_LOGIN_RESULT_OK = "OK";
        private const string PROTOCOL_DOWNLOAD_LOG = "LF";
        private const string PROTOCOL_DELIMITER = ",";
        private const char PROTOCOL_DELIMITER_CHAR = ',';
        private const int PROTOCOL_COMMAND_LEVEL1 = 0;
        private const int PROTOCOL_COMMAND_LEVEL2 = 1;
        private const int PROTOCOL_COMMAND_LEVEL3 = 2;
        private const string DATETIME_STRING_FORMAT = "yyyy-MM-dd HH:mm:ss";
        private const string ERROR_MESSAGE_SERVER_SHUTDOWN = "Server is shutdowned. ";
        private const string ERROR_MESSAGE_FAILED_TO_CONNECT_TO_SERVER = "Error to connect to Sauna Server. ";
 
        // Private variables
        private object lockObject;
        private LoggerFactoryImplementation loggerFactory;
        private ILogger fileLogger;
        private static TcpClient tcpClient;
        private static bool isConnectedToServer = false;

        // Private static singleton
        private static TcpDaoGatewaySsl tcpDaoGatewaySslSingleton = new TcpDaoGatewaySsl();

        // Public enumeration
        public enum LoginResult { SUCCESS = 1, USER_REJECTED = 2, PROTOCOL_ERROR = 3 }

        /// <summary>
        /// Private constructor for singleton
        /// </summary>
        private TcpDaoGatewaySsl()
        {
            lockObject = new object();
        }

        /// <summary>
        /// Private destructor for singleton
        /// </summary>
        ~TcpDaoGatewaySsl()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
            }
        }

        /// <summary>
        /// Get singleton instance
        /// </summary>
        public static TcpDaoGatewaySsl Instance(NetworkSetting networkSetting)
        {
            // Initialize Ftp Setting
            tcpDaoGatewaySslSingleton.HostAddress = networkSetting.TcpHostAddress;
            tcpDaoGatewaySslSingleton.PortNumber = networkSetting.TcpPortNumber;
            tcpDaoGatewaySslSingleton.UseSsl = networkSetting.TcpSsl;
            tcpDaoGatewaySslSingleton.LogFileDirectory = networkSetting.LogFileDirectory;
            tcpDaoGatewaySslSingleton.loggerFactory = new LoggerFactoryImplementation();
            tcpDaoGatewaySslSingleton.fileLogger = tcpDaoGatewaySslSingleton.loggerFactory.Make(FILE_LOGGER);

            try
            {
                tcpClient = new TcpClient(tcpDaoGatewaySslSingleton.HostAddress, tcpDaoGatewaySslSingleton.PortNumber);
                isConnectedToServer = true;
            }
            catch (SocketException e)
            {
                // Fail to connect to Sauna Server
                tcpClient = null;
                isConnectedToServer = false;
                Console.WriteLine(e);
            }

            return tcpDaoGatewaySslSingleton;
        }

        /// <summary>
        /// Send data to server by TCP/IP
        /// </summary>
        /// <param name="sendData">Send data to server</param>
        private static void SendDataToServer(string sendData)
        {
            try
            {
                byte[] bytes = Encoding.Unicode.GetBytes(sendData);
                tcpClient.GetStream().Write(bytes, 0, bytes.Length);
            }
            catch(IOException e) 
            {
                Console.WriteLine(ERROR_MESSAGE_SERVER_SHUTDOWN + e);
            }
        }

        /// <summary>
        /// Receive data from server by TCP/IP
        /// <param name="sendData">Send data to server</param>
        /// </summary>
        /// <returns>Received String Data</returns>
        private String ReceiveDataFromServer()
        {
            String receiveData = "";
            bool done = false;

            try
            {
                while (!done)
                {
                    byte[] buffer = new byte[MAX_RECEIVE_DATA_SIZE];
                    int totalRead = 0;

                    do
                    {
                        int readCnt = tcpClient.GetStream().Read(buffer, totalRead, buffer.Length - totalRead);
                        totalRead += readCnt;
                    } while (tcpClient.GetStream().DataAvailable);

                    receiveData = Encoding.Unicode.GetString(buffer, 0, totalRead);
                    done = true;
                }
            }
            catch (Exception e)
            {
                throw new Exception(e.ToString());
            }
            return receiveData;
        }

        /// <summary>
        /// Send login request to server
        /// </summary>
        /// <param name="sendData">Send data to server</param>
        /// <returns>Tcp Transfer Status</returns>
        public TcpTransferStatus SendLoginRequestToServer(string userId, string password)
        {
            try
            {
                if (!isConnectedToServer)
                {
                    tcpClient = new TcpClient(tcpDaoGatewaySslSingleton.HostAddress, tcpDaoGatewaySslSingleton.PortNumber);
                    isConnectedToServer = true;
                }

                SendDataToServer(PROTOCOL_PREFIX + PROTOCOL_DELIMITER + PROTOCOL_LOGIN_REQUEST
                               + PROTOCOL_DELIMITER + userId + PROTOCOL_DELIMITER + password);

                String responseData = ReceiveDataFromServer();

                Console.WriteLine(responseData + System.Environment.NewLine);

                return ValidateLoginResponse(responseData);
            }
            catch (SocketException e)
            {
                // Fail to connect to Sauna Server
                tcpClient = null;
                isConnectedToServer = false;
                Console.WriteLine(e);

                // Log connection error
                fileLogger.Write(ERROR_MESSAGE_FAILED_TO_CONNECT_TO_SERVER + e.ToString());
                return new TcpTransferStatus(TcpTransferStatus.Status.CONNECT_ERROR);
            }
        }

        /// <summary>
        /// Send downloaded log to server
        /// </summary>
        /// <param name="userId">User ID</param>
        /// <param name="fileName">File Name</param>
        /// <param name="datetime">Download date time</param>
        /// <returns>Tcp Transfer Status</returns>
        public TcpTransferStatus SendDownloadLogToServer(string userId, string fileName, DateTime dateTime)
        {
            try
            {
                if (!isConnectedToServer)
                {
                    tcpClient = new TcpClient(tcpDaoGatewaySslSingleton.HostAddress, tcpDaoGatewaySslSingleton.PortNumber);
                    isConnectedToServer = true;
                }

                // Convert datetime into string
                string stDateTime = dateTime.ToString(DATETIME_STRING_FORMAT);

                SendDataToServer(PROTOCOL_PREFIX + PROTOCOL_DELIMITER + PROTOCOL_DOWNLOAD_LOG
                               + PROTOCOL_DELIMITER + userId + PROTOCOL_DELIMITER + fileName
                               + PROTOCOL_DELIMITER + stDateTime);

                return new TcpTransferStatus(TcpTransferStatus.Status.SUCCESS);
            }
            catch (SocketException e)
            {
                // Fail to connect to Sauna Server
                tcpClient = null;
                isConnectedToServer = false;
                Console.WriteLine(e);
                return new TcpTransferStatus(TcpTransferStatus.Status.CONNECT_ERROR);
            }
        }

        /// <summary>
        /// Validate Login Response data
        /// </summary>
        /// <param name="sendData">Received response data from server</param>
        /// <returns>Tcp Transfer Status</returns>
        private TcpTransferStatus ValidateLoginResponse(String responseData)
        {
            string[] protocolCommand;

            protocolCommand = responseData.Split(new char[] { PROTOCOL_DELIMITER_CHAR });

            if (protocolCommand[PROTOCOL_COMMAND_LEVEL1] != PROTOCOL_PREFIX)
            {
                return new TcpTransferStatus(TcpTransferStatus.Status.PROTOCOL_ERROR);
            }
            if (protocolCommand[PROTOCOL_COMMAND_LEVEL2] != PROTOCOL_LOGIN_RESPONSE)
            {
                return new TcpTransferStatus(TcpTransferStatus.Status.PROTOCOL_ERROR);
            }
            if (protocolCommand[PROTOCOL_COMMAND_LEVEL3] == PROTOCOL_LOGIN_RESULT_BAD)
            {
                return new TcpTransferStatus(TcpTransferStatus.Status.USER_REJECTED);
            }
            if (protocolCommand[PROTOCOL_COMMAND_LEVEL3] == PROTOCOL_LOGIN_RESULT_OK)
            {
                return new TcpTransferStatus(TcpTransferStatus.Status.SUCCESS);
            }
            return new TcpTransferStatus(TcpTransferStatus.Status.PROTOCOL_ERROR);
        }

        /// <summary>
        /// Check if client is connected server
        /// </summary>
        /// <returns>Connection Status</returns>
        public bool IsConnectedToServer()
        {
            return isConnectedToServer;
        }
    }
}
